package com.szl.group.config.aop;

/**
 * description:
 * author: shenxiaolong
 * date: 2020/6/21  1:06
 */

import com.szl.group.common.anno.SpelOptoin;
import com.szl.group.common.anno.WebLog;
import com.szl.group.utils.JoinPointUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author user
 */
@Aspect     // 表示一个切面bean
@Component  // bean容器的组件注解。虽然放在contrller包里，但它不是控制器。如果注入service,但我们又没有放在service包里
@Order(3)   // 有多个日志时，ORDER可以定义切面的执行顺序（数字越大，前置越后执行，后置越前执行）
@Slf4j
public class WebLogAspect {

    //线程副本类去记录各个线程的开始时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    private final ApplicationContext applicationContext;

    public WebLogAspect(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    //定义切入点
	/*1、execution 表达式主体
	  2、第1个* 表示返回值类型  *表示所有类型
	  3、包名  com.*.*.controller下
	  4、第4个* 类名，com.*.*.controller包下所有类
	  5、第5个* 方法名，com.*.*.controller包下所有类所有方法
	  6、(..) 表示方法参数，..表示任何参数
	  */
    @Pointcut("execution(public * com.*.*.controller.*.*(..))")
    public void webLog() {

    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        //方法里面注入连接点
        log.debug("前置通知：");
        //info ,debug ,warn ,erro四种级别，这里我们注入info级别
        startTime.set(System.currentTimeMillis());

        //获取servlet请求对象---因为这不是控制器，这里不能注入HttpServletRequest，但springMVC本身提供ServletRequestAttributes可以拿到
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        log.debug("URL:" + request.getRequestURL().toString());         // 想那个url发的请求
        log.debug("METHOD:" + request.getMethod());
        log.info("CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName()
                + "."
                + joinPoint.getSignature().getName());                     // 请求的是哪个类，哪种方法
        log.info("ARGS:" + Arrays.toString(joinPoint.getArgs()));     // 方法本传了哪些参数
    }


    //方法的返回值注入给ret
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfter(Object ret) {
        log.debug("后置通知：");
        log.info("RESPONSE:" + ret);       // 响应的内容---方法的返回值responseEntity
        log.info("SPEND:" + (System.currentTimeMillis() - startTime.get()));
    }


    private static final String regex = "\\{([^}]*)\\}";


    //@Before("webLog()")
    public void doBefore2(JoinPoint jp) {

        WebLog webLog = JoinPointUtils.getTargetAnnotation(jp, WebLog.class);
        SpelOptoin[] options = webLog.option();

        try {

            String desc = webLog.format();

            //获取所有el表达式
            List<String> spelArgs = getSpelArgs(desc);
            //用{}替换所有的{xxx}
            desc = desc.replaceAll("\\{[^}]*\\}", "{}");

            if (spelArgs.size() != 0) {

                //根据el表达式获取具体值
                String[] spels = spelEval(spelArgs, jp.getArgs());
                replaceRealValue(options, spels);
                //填充表达式
                desc = MessageFormatter.arrayFormat(desc, spels).getMessage();
            }

            //拼接完整描述
            if (webLog.prefix()) {
                //      desc = mergeContent(desc, userHolder);
            }
            log.info(desc);
            //插入数据库
            //  insertLog(userHolder, desc, loginTypeEnum);

        } catch (Exception ex) {
            log.error("", ex);
        }
    }

    /**
     * 替换实际的值
     *
     * @param options
     * @param spels
     */
    private void replaceRealValue(SpelOptoin[] options, String[] spels) {
        if (options.length == 0)
            return;
        for (SpelOptoin option : options) {
            Class<?> handler;
            if (HandlerEvaluation.class.isAssignableFrom(handler = option.handler())) {
                HandlerEvaluation bean = (HandlerEvaluation) applicationContext.getBean(handler);
                spels[option.index()] = bean.map(spels[option.index()]);
            } else {
                String[] split = option.optional();
                spels[option.index()] = choose(split, spels[option.index()]);
            }

        }

    }

    /**
     * 提取el表达式
     *
     * @param desc 待格式化参数
     * @return
     */
    private List<String> getSpelArgs(String desc) {

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(desc);
        List<String> spelArgs = new ArrayList<>(4);

        //解析el表达式
        while (matcher.find()) {
            String group = matcher.group();
            group = group.substring(1, group.length() - 1);
            spelArgs.add(group);
        }

        return spelArgs;
    }

    /**
     * 根据key，获取value
     *
     * @param options k-v数组
     * @param key
     * @return
     */
    private String choose(String[] options, String key) {

        return Arrays.stream(options)
                .filter(temp -> temp.startsWith(key))
                .map(temp -> temp.split(":")[1])
                .findFirst()
                .orElse("");
    }

/*    private void insertLog(ThreadUserContext.UserHolder userHolder, String desc, LoginTypeEnum loginTypeEnum) {
        UserOperateLog userOperateLog = new UserOperateLog();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        userOperateLog.setCreateTime(sdf.format(new Date()));
        userOperateLog.setId(Tools.uuidCreate());
        userOperateLog.setUserName(userHolder.getUserName());
        userOperateLog.setAccountLevel(userHolder.getAccountLevel());
        // userOperateLog.setOperateType();
        userOperateLog.setOperateUserId(userHolder.getUserId());
        userOperateLog.setParentId(userHolder.getParentId());
        userOperateLog.setOperateDesc(desc);
        userOperateLog.setUrl(userHolder.getUrl());
        userOperateLog.setEndPort(loginTypeEnum.getCode());
        userOperateLog.setIp(userHolder.getIp());
        userOperateLogMapping.insert(userOperateLog);
    }

    *//**
     * 拼接完整log日志
     *
     * @param desc
     * @param userHolder
     * @return
     *//*
    private String mergeContent(String desc, ThreadUserContext.UserHolder userHolder) {

        StringBuilder content = new StringBuilder();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return content
                .append("【").append(sdf.format(new Date())).append("】")
                .append(RoleEnum.chooseName(userHolder.getAccountLevel()))
                .append("【").append(userHolder.getUserName()).append("】")
                .append(desc)
                .toString();

    }*/

    /**
     * 替换参数
     *
     * @param spel
     * @param object
     * @return
     */
    private String[] spelEval(List<String> spel, Object object) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext(object);
        return spel.stream()
                .map(one -> parser.parseExpression(one).getValue(context, String.class))
                .toArray(String[]::new);
    }


}
